#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "scan.h"
#include "global.h"

char tokenString[MAXTOKENLEN+1];

struct Token *token_list;

/* states in scanner DFA */
typedef enum { 
  INID,INNUM,INLTEQ,INGTEQ,INEQ,INNEQ,
  INCOMMENT,START,DONE
} state_t;

/* BUFLEN = length of the input buffer for source code lines */
#define BUFLEN 256

static char line_buf[BUFLEN]; /* holds the current line */
static int line_pos = 0; /* current position in line_buf */
static int lineno = 0;
static int buf_size = 0; /* current size of buffer string */
static bool eof_flag = 0; /* corrects unget_next_char behavior on EOF */

// Reserve words
static struct {
  char *str;
  tokentype_t token;
} reserved_words[10]={
    {"if",IF},{"else",ELSE},{"while",WHILE},
    {"return",RETURN},{"void",VOID},{"int",INT},{NULL, ERROR}
};

static void unget_next_char() {
  --line_pos;
}

static tokentype_t search_reserved (char * s) { 
  int i;
  for (i=0;;i++)
    if (reserved_words[i].str != NULL && !strcmp(s, reserved_words[i].str))
      return reserved_words[i].token;
  return ID;
}

static int get_next_char(void) { 
  if (!(line_pos < buf_size)) { 
    lineno++;
    if (fgets(line_buf,BUFLEN-1,source)) { 
      buf_size = strlen(line_buf);
      line_pos = 0;
      return line_buf[line_pos++];
    } else { 
      eof_flag = 1;
      return EOF;
    }
  }
  else return line_buf[line_pos++];
}

tokentype_t get_token() {
  /* index for storing into tokenString */
  int tokenStringIndex = 0;
  /* holds current token to be returned */
  tokentype_t currentToken;
  /* current state - always begins at START */
  state_t state = START;
  /* flag to indicate save to tokenString */

  int save;
  while (state != DONE) {
    int c = get_next_char();
    switch (state) {
    case START:
      if (isdigit(c)) {
        state = INNUM;
      } else if (isalpha(c)) {
        state = INID;
      } else if (c == '<') {
        state = INLTEQ;
      } else if (c == '>') {
        state = INGTEQ;
      } else if (c == '=') {
        state = INEQ;
      } else if (c == '!') {
        state == INNEQ;
      } else if (c == '/') {
        c = get_next_char();
        if (c != '*') {
          unget_next_char();
          currentToken = DIV;
          state = DONE;
        } else {
          state = INCOMMENT;
          save = 0;
        }
      } else if (isspace(c)) {
        save = 0;
      } else if (c == '+') {
        currentToken = PLUS;
      } else if (c == '-') {
        currentToken = MINUS;
      } else if (c == '*') {
        currentToken = MUL;
      } else if (c == ';') {
        currentToken = SEMI;
      } else if (c == '(') {
        currentToken = LPAREN;
      } else if (c == ')') {
        currentToken = RPAREN;
      } else if (c == '[') {
        currentToken = LSQUAR;
      } else if (c == ']') {
        currentToken = RSQUAR;
      } else if (c == '{') {
        currentToken = LBRACE;
      } else if (c == '}') {
        currentToken = RBRACE;
      } else if (c == EOF) {
        currentToken = ENDFILE;
      } else {
        save = 0;
        currentToken = ERROR;
      }
      break;
    case INID:
      if (!isalpha(c)) {
        unget_next_char();
        currentToken = ID;
        state = DONE;
      }
      break;
    case INNUM:
      if (!isdigit(c)) {
        unget_next_char();
        currentToken = NUMBER;
        state = DONE;
      }
      break;
    case INLTEQ:
      if (c == '=') {
        currentToken = LTEQ;
        state = DONE;
      } else {
        unget_next_char();
        currentToken = LT;
        state = DONE;
        save = 0;
      }
      break;
    case INGTEQ:
      if (c == '=') {
        currentToken = GTEQ;
        state = DONE;
      } else {
        unget_next_char();
        currentToken = GT;
        state = DONE;
        save = 0;
      }
      break;
    case INEQ:
      if (c == '=') {
        currentToken = EQ;
        state = DONE;
      } else {
        unget_next_char();
        currentToken = ASSIGN;
        state = DONE;
        save = 0;
      }
      break;
    case INNEQ:
      if (c == '=') {
        currentToken = NEQ;
        state = DONE;
      } else {
        unget_next_char();
        currentToken = ERROR;
        state = DONE;
        save = 0;
      }
      break;
    case INCOMMENT:
      if (c == '*') {
        c = get_next_char();
        if (c == '/') {
          state = START;
        } else {
          unget_next_char();
        }
      }
      save = 0;
      break;
    }
    if (save == 1 && tokenStringIndex < MAXTOKENLEN) {
      tokenString[tokenStringIndex++] = (char)c;
    } else {
      save = 1;
    }
    if (state == DONE) {
      tokenString[tokenStringIndex] = '\0';
      if (currentToken == ID) {
        search_reserved(tokenStringIndex);
      }
    }
  }
  return currentToken;
}
